<?php
/**
 * Created by PhpStorm
 * Author Hui
 * Date 2024/5/19   16:25
 */

namespace designpatterns;

use Closure;
use designpatterns\container\ContainerInterface;
use designpatterns\exception\ClassNotFoundException;
use designpatterns\exception\FuncNotFoundException;
use designpatterns\helper\Str;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionFunctionAbstract;

class Container implements ContainerInterface
{
    /**
     * 容器对象实例
     * @var Container|Closure
     */
    protected static $instance;

    /**
     * 容器中的对象实例
     * @var array
     */
    protected $instances = [];

    /**
     * 容器绑定标识
     * @var array
     */
    protected $bind = [];

    /**
     * 容器回调
     * @var array
     */
    protected $invokeCallback = [];


    /**
     * 设置当前容器
     * @param $instance
     * @return void
     */
    public function setInstance($instance) : void
    {
        self::$instance = $instance;
    }

    /**
     * 获取当前容器实例(单例)
     * @return Closure|Container|mixed
     */
    public static function getInstance()
    {
        if(is_null(static::$instance)){
            static::$instance = new static;
        }

        if(static::$instance instanceof Closure){
            return (static::$instance)();
        }

        return static::$instance;
    }


    public function make(string $abstract, array $vars = [], bool $newInstance = false)
    {
        $abstract = $this->getAlias($abstract);

        if(isset($this->instances[$abstract]) && !$newInstance){
            return $this->instances[$abstract];
        }

        /*if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
        } else {
            $object = $this->invokeClass($abstract, $vars);
        }*/

        $object = $this->invokeClass($abstract, $vars);

        if(!$newInstance){
            $this->instances[$abstract] = $object;
        }
        return  $object;
    }


    /**
     * 执行invokeClass回调
     * @param string $class
     * @param $object
     * @return void
     */
    protected function invokeAfter(string $class, $object): void
    {
        if (isset($this->invokeCallback['*'])) {
            foreach ($this->invokeCallback['*'] as $callback) {
                $callback($object, $this);
            }
        }

        if (isset($this->invokeCallback[$class])) {
            foreach ($this->invokeCallback[$class] as $callback) {
                $callback($object, $this);
            }
        }
    }


    /**
     * 调用反射执行类的实例化 支持依赖注入
     * @param string $class
     * @param array $vars
     * @return mixed|object|null
     * @throws ReflectionException
     */
    public function invokeClass(string $class,array $vars)
    {
        try {
            $reflect = new ReflectionClass($class);
        } catch (ReflectionException $e) {
            throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
        }

        if ($reflect->hasMethod('__make')) {
            $method = $reflect->getMethod('__make');
            if ($method->isPublic() && $method->isStatic()) {
                $args   = $this->bindParams($method, $vars);
                $object = $method->invokeArgs(null, $args);
                $this->invokeAfter($class, $object);
                return $object;
            }
        }

        $constructor = $reflect->getConstructor();

        $args = $constructor ? $this->bindParams($constructor, $vars) : [];

        $object = $reflect->newInstanceArgs($args);

        $this->invokeAfter($class, $object);

        return $object;
    }


    /*public function invokeFunction($function, array $vars = [])
    {
        try {
            $reflect = new ReflectionFunction($function);
        } catch (ReflectionException $e) {
            throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
        }

        $args = $this->bindParams($reflect, $vars);

        return $function(...$args);
    }*/

    public function getAlias(string $abstract): string
    {
        if(isset($this->bind[$abstract])){
            $bind = $this->bind[$abstract];

            if(is_string($bind)){
                return $this->getAlias($bind);
            }
        }

        return $abstract;
    }


    /**
     * 綁定參數
     * @param ReflectionFunctionAbstract $reflect 反射類
     * @param array $vars 參數
     * @return array
     */
    public function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []) : array
    {
        if($reflect->getNumberOfParameters() == 0){
            return [];
        }

        // 判断数组类型 数字数组时按顺序绑定参数
        reset($vars);
        $type   = key($vars) === 0 ? 1 : 0;
        $params = $reflect->getParameters();
        $args   = [];

        foreach ($params as $param) {
            $name           = $param->getName();
            $lowerName      = Str::snake($name);
            $reflectionType = $param->getType();

            if ($param->isVariadic()) {
                return array_merge($args, array_values($vars));
            } elseif ($reflectionType && $reflectionType instanceof \ReflectionNamedType && $reflectionType->isBuiltin() === false) {
                $args[] = $this->getObjectParam($reflectionType->getName(), $vars);
            } elseif (1 == $type && !empty($vars)) {
                $args[] = array_shift($vars);
            } elseif (0 == $type && array_key_exists($name, $vars)) {
                $args[] = $vars[$name];
            } elseif (0 == $type && array_key_exists($lowerName, $vars)) {
                $args[] = $vars[$lowerName];
            } elseif ($param->isDefaultValueAvailable()) {
                $args[] = $param->getDefaultValue();
            } else {
                throw new InvalidArgumentException('method param miss:' . $name);
            }
        }

        return $args;
    }


    /**
     * 获取对象类型的参数值
     * @param string $className
     * @param array $vars
     * @return mixed|void
     */
    protected function getObjectParam(string $className, array &$vars)
    {
        $array = $vars;
        $value = array_shift($array);

        if ($value instanceof $className) {
            $result = $value;
            array_shift($vars);
        } else {
            $result = $this->make($className);
        }

        return $result;
    }

    public function get(string $id)
    {
        // TODO: Implement get() method.
    }

    public function has(string $id)
    {
        // TODO: Implement has() method.
    }
}